﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="InvokeMethodTests.cs" company="JDML Consulting Ltd.">
//   Copyright 2011 Photon Project - http://photon.codeplex.com
// </copyright>
// <summary>
//   Defines the InvokeMethodTests type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Photon.Windows.Input
{
    using System;
    using System.ComponentModel.DataAnnotations;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Interactivity;
    using Assets;
    using Diagnostics;
    using Diagnostics.Assets;
    using Microsoft.Silverlight.Testing;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Windows;
    using EventTrigger = System.Windows.Interactivity.EventTrigger;

    [TestClass]
    public class InvokeMethodTests : SilverlightTest
    {
        #region Fields

        public TestModel Target1;
        public TestModel Target2;
        public TestModel DataContext1;
        public TestModel DataContext2;

        #endregion

        #region Types

        public class TestTree
        {
            public TestTree()
            {
                Container = new StackPanel();
                Container.Children.Add((Button = new Button()));
                var triggers = Interaction.GetTriggers(Button);
                triggers.Add(new EventTrigger() {EventName = "Click"});
                InvokeMethod = new InvokeMethod();
                triggers[0].Actions.Add(InvokeMethod);

            }

            public EventTrigger Trigger { get { return Interaction.GetTriggers(Button)[0] as EventTrigger; } }

            public TestTree(object dataContext) : this()
            {
                this.Container.DataContext = dataContext;
            }

            public StackPanel Container { get; private set; }

            public Button Button { get; private set; }

            public InvokeMethod InvokeMethod { get; private set; }
        }

        public class TestModelEventArgs : EventArgs
        {
            public TestModelEventArgs(object payload)
            {
                Payload = payload;
            }

            public object Payload { get; private set; }
        }

        public class TestModel 
        {
            public TestModel()
            {
                CanInvokeResult = true;
            }

            public void Invoke()
            {
            }
            
            public void Invoke(int value)
            {
                
            }

            public bool CanInvoke()
            {
                return this.CanInvokeResult;
            }

            public bool CanInvokeResult { get; set; }

            public void EventReceived(object argument)
            {
                this.LastEventReceived = argument;
            }

            public object LastEventReceived { get; set; }

            public void ValidatedInvoke([Range(0, 100)] int value)
            {
                
            }

            public void RaiseEvent(object payload)
            {
                if (EventRaised != null)
                {
                    EventRaised(this, new TestModelEventArgs(payload));
                }
            }

            public event EventHandler<TestModelEventArgs> EventRaised;
        }

        #endregion

        [TestInitialize]
        public void TestInitialize()
        {
            Target1 = new TestModel();
            Target2 = new TestModel();
            DataContext1 = new TestModel();
            DataContext2 = new TestModel();
        }

        [TestCleanup]
        public void TestCleanup()
        {
            ScopePropertyFallback.Instance.ClearValue(Invoke.TargetProperty);
        }

        [TestMethod]
        public void When_changing_between_target_binding_and_data_context_binding_should_update()
        {
            var testTree = new TestTree();

            ScopePropertyFallback.Instance.SetValue(Invoke.TargetProperty, Target1);
            Assert.AreEqual(Target1, testTree.InvokeMethod.Target);
            
            testTree.Container.DataContext = DataContext1;
            Assert.AreEqual(Target1, testTree.InvokeMethod.Target);

            ScopePropertyFallback.Instance.ClearValue(Invoke.TargetProperty);
            Assert.AreEqual(DataContext1, testTree.InvokeMethod.Target);

            testTree.Container.DataContext = DataContext2;
            Assert.AreEqual(DataContext2, testTree.InvokeMethod.Target);

            ScopePropertyFallback.Instance.SetValue(Invoke.TargetProperty, Target1);
            Assert.AreEqual(Target1, testTree.InvokeMethod.Target);
        }

        [TestMethod]
        public void When_valid_method_name_is_set_should_bind()
        {
            var testTree = new TestTree(DataContext1);
            Assert.IsNull(testTree.InvokeMethod.MethodBinding);
            testTree.InvokeMethod.MethodName = "Invoke";
            Assert.IsNotNull(testTree.InvokeMethod.MethodBinding);
        }

        [TestMethod]
        public void When_invalid_method_name_is_set_should_not_bind()
        {
            var testTree = new TestTree(DataContext1);
            Assert.IsNull(testTree.InvokeMethod.MethodBinding);
            testTree.InvokeMethod.MethodName = "InvalidInvoke";
            Assert.IsNull(testTree.InvokeMethod.MethodBinding);
        }

        [TestMethod]
        public void When_method_name_is_changed_from_valid_to_invalid_should_unbind()
        {
            var testTree = new TestTree(DataContext1) { InvokeMethod = { MethodName = "Invoke" } };
            Assert.IsNotNull(testTree.InvokeMethod.MethodBinding);
            testTree.InvokeMethod.MethodName = "InvalidInvoke";
            Assert.IsNull(testTree.InvokeMethod.MethodBinding);
        }

        [TestMethod]
        public void When_valid_method_name_then_valid_argument_types_are_set_should_set_can_execute_to_true()
        {
            var testTree = new TestTree(DataContext1) { InvokeMethod = { MethodName = "Invoke(int)" } };
            
            //  bind method (arguments should be invalid)
            Assert.IsNotNull(testTree.InvokeMethod.MethodBinding);
            Assert.IsFalse(testTree.InvokeMethod.CanExecute);

            // add argument and verify we can execute
            testTree.InvokeMethod.Arguments.Add(new Argument() { Value = 1 });
            Assert.IsTrue(testTree.InvokeMethod.CanExecute);
        }

        [TestMethod]
        public void When_valid_argument_types_are_set_then_valid_method_name_is_set_should_set_can_execute_to_true()
        {
            var testTree = new TestTree(DataContext1);

            testTree.InvokeMethod.Arguments.Add(new Argument { Value = 1 });
            Assert.IsFalse(testTree.InvokeMethod.CanExecute);
            Assert.IsNull(testTree.InvokeMethod.MethodBinding);

            //  bind method (arguments should be invalid)
            testTree.InvokeMethod.MethodName = "Invoke(int)";
            Assert.IsTrue(testTree.InvokeMethod.CanExecute);
        }

        [TestMethod]
        public void When_argument_type_becomes_invalid_should_set_can_execute_to_false()
        {
            var testTree = new TestTree(DataContext1);

            testTree.InvokeMethod.MethodName = "Invoke(int)";
            var argument = new Argument() {Value = 1};
            testTree.InvokeMethod.Arguments.Add(argument);
            Assert.IsNotNull(testTree.InvokeMethod.MethodBinding);
            Assert.IsTrue(testTree.InvokeMethod.CanExecute);

            argument.Value = null;
            Assert.IsFalse(testTree.InvokeMethod.CanExecute);
        }
        
        [TestMethod]
        public void When_argument_fails_validation_should_set_can_execute_to_false()
        {
            var testTree = new TestTree(DataContext1) { InvokeMethod = { MethodName = "ValidatedInvoke(int)" } };

            var argument = new Argument() { Value = 1 };
            testTree.InvokeMethod.Arguments.Add(argument);
            Assert.IsNotNull(testTree.InvokeMethod.MethodBinding);
            Assert.IsTrue(testTree.InvokeMethod.CanExecute);

            argument.Value = 200;
            Assert.IsFalse(testTree.InvokeMethod.CanExecute);

            argument.Value = 1;
            Assert.IsTrue(testTree.InvokeMethod.CanExecute);
        }

        [TestMethod, Asynchronous]
        public void Should_trace_missing_method_binding_errors()
        {
            var testTraceWriter = new TestTraceWriter();
            Trace.TraceWriter = testTraceWriter;
            
            var view = new MissingMethodBindingErrorsView();
            view.Loaded += (s, e) =>
            {
                this.EnqueueCallback(() =>
                {
                    Assert.AreEqual(2, testTraceWriter.Lines.Count);
                    Assert.IsTrue(testTraceWriter.RemoveMatch(
                        string.Format(ExceptionMessages.MethodInvokeActionBindingError, "MissingDataContextMethod", typeof(MissingMethodBindingErrorsViewModel).FullName, "DataContext")));
                    Assert.IsTrue(testTraceWriter.RemoveMatch(
                        string.Format(ExceptionMessages.MethodInvokeActionBindingError, "MissingInvokeTargetMethod", typeof(MissingMethodBindingErrorsViewModel).FullName, "Invoke.Target { SourceType = System.Windows.Controls.Button, SourceName = 'MissingInvokeTarget' }")));
                });
                this.EnqueueTestComplete();
            };

            this.TestPanel.Children.Add(view);
        }


        /// <summary>
        /// Should hold strong reference to datacontact change delegate
        /// </summary>
        /// <remarks>
        /// This test covers a defect that caused data context changes to not be tracked
        /// due to a strong reference not being held on the delegate used to subscribe to 
        /// a weak event.
        /// </remarks>
        [TestMethod]
        public void Should_hold_strong_reference_to_datacontext_change_delegate()
        {
            // setup initial context
            var testTree = new TestTree(DataContext1) { InvokeMethod = { MethodName = "Invoke" } };
            Assert.AreSame(testTree.InvokeMethod.Target, DataContext1);
            
            // force a collection
            GC.Collect();

            testTree.Container.DataContext = DataContext2;
            Assert.AreSame(testTree.InvokeMethod.Target, DataContext2);
        }

        [TestMethod]
        public void Should_detect_changes_to_datacontext()
        {
            // setup initial context
            var testTree = new TestTree(DataContext1) { InvokeMethod = { MethodName = "Invoke" } };
            Assert.AreSame(testTree.InvokeMethod.Target, DataContext1);

            Assert.IsTrue(testTree.InvokeMethod.CanExecute);
            DataContext2.CanInvokeResult = false;
            testTree.Container.DataContext = DataContext2;
            Assert.IsFalse(testTree.InvokeMethod.CanExecute);
        }


        [TestMethod]
        public void Should_respond()
        {
            var testTree = new TestTree(DataContext1);
            testTree.Trigger.EventName = null;
            testTree.Trigger.SourceObject = DataContext1;
            testTree.Trigger.EventName = "EventRaised";
            testTree.InvokeMethod.Arguments.Add(
                new TriggerContextArgument()
                    {
                        Binding = new Binding("Payload")
                    });
            testTree.InvokeMethod.MethodName = "EventReceived";
            var payload = new object();
            DataContext1.RaiseEvent(payload);
            Assert.AreSame(payload, DataContext1.LastEventReceived);
        }
    }
}
